package com.cjym.GateWay.filters;

import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Component
@Slf4j
public class CurrentLimitingFilter implements GlobalFilter, Ordered {
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String uri = request.getPath().value();
        Set<String> keys = stringRedisTemplate.keys("service::project*");
        Map<String, Map<String, String>> entries = new HashMap<>();
        if (keys != null) {
            for (String key : keys) {
                HashOperations<String, String, String> stringObjectObjectHashOperations = stringRedisTemplate.opsForHash();
                Map<String, String> entry =stringObjectObjectHashOperations.entries(key);
                entries.put(key, entry);
            }
        }
        for (Map<String,String> entry : entries.values()){
            String regular = entry.get("regular");
            regular=regular.replaceAll("\n","");
            String name = entry.get("name");
            name=name.replaceAll("\n","");
            int time = Integer.parseInt(entry.get("time"))*1000;
            int current = Integer.parseInt(entry.get("current"));
            Matcher matcher = Pattern.compile(regular).matcher(uri);
            if(matcher.matches()){
                log.info("正则已匹配，限流的服务为{}",name);
                long currentTime = System.currentTimeMillis();
                uri+="/"+currentTime;
                long fiveSecondsAgo = currentTime - time;
//                ZSetOperations<String, String> zSetOps = stringRedisTemplate.opsForZSet();
//                zSetOps.removeRangeByScore("regex_requests_"+name, 0, fiveSecondsAgo);
//                Set<String> requests = zSetOps.rangeByScore("regex_requests_"+name, fiveSecondsAgo, currentTime);
//                if (requests == null || requests.size() <= current-1) {
//                    zSetOps.add("regex_requests_"+name, uri, currentTime);
                String lua= """
                        local currentTime = tonumber(ARGV[1])
                        local fiveSecondsAgo = tonumber(ARGV[2])
                        local requestLimit = tonumber(ARGV[3])
                        local uri = ARGV[4]
                        local zSetOps = redis.call('zrangebyscore', KEYS[1], fiveSecondsAgo, currentTime)
                        if (not zSetOps or #zSetOps <= requestLimit) then
                            redis.call('zremrangebyscore', KEYS[1], 0, fiveSecondsAgo)
                            redis.call('zadd', KEYS[1], currentTime, uri)
                            return 1
                        else
                            return 0
                        end
                        """;
                DefaultRedisScript<Boolean> redisScript = new DefaultRedisScript<>();
                redisScript.setScriptText(lua);
                redisScript.setResultType(Boolean.class);
                Boolean execute = stringRedisTemplate.execute(redisScript, Collections.singletonList("regex_requests_" + name),
                        String.valueOf(currentTime), String.valueOf(fiveSecondsAgo), String.valueOf(current-1), uri);
                if(Boolean.TRUE.equals(execute)){
                    return chain.filter(exchange);
                }else{
                    log.error("请求已被拦截,超出访问限制");
                    exchange.getResponse().setRawStatusCode(HttpStatus.CONFLICT.value());
                    exchange.getResponse().setStatusCode(HttpStatus.CONFLICT);
                    return exchange.getResponse().setComplete();
                }
            }
        }
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 6;
    }

    @PostConstruct
    private void init(){
        log.info("开启路径限流");
    }
}

